<?php
namespace Monkey\Database\SqlHelper;

use Monkey\CacheManager;
use Monkey\Database\QueryHelper as QueryHelper;

/**
 * 查询选择操作助手\Monkey\Database\SqlHelper\Select
 * @package    Monkey\Database\SqlHelper
 * @author     HuangYi
 * @copyright  Copyright (c) 2011-07-01——2013-03-30
 * @license    New BSD License
 * @version    $Id: \Monkey\Database\SqlHelper\Select.php 版本号 2013-03-30 $
 */
class Select
{
    /**
     * @var \Monkey\Database\QueryHelper\_Interface
     */
    private $_queryHelper;
    private $_options;
    private $_linkKey;
    public function __construct(){}
    /**
     * @param string $linkKey 连接键名
     * @param \Monkey\Database\QueryHelper\_Interface &$queryHelper
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function _reset( $linkKey, QueryHelper\_Interface &$queryHelper){
        $this->_linkKey=$linkKey;
        $this->_queryHelper=$queryHelper;
        $this->_options=array();
        return $this;
    }
    /**
     * 将select语句选出的结果集插入到另一个表中
     * 重要说明，into语句不能复制主键的索引
     * @param string $table 插入的表名，也可以是新表名
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function into($table){
        $this->_options['into']='[:{:'.$table.':}:]';
        return $this;
    }
    /**
     * 去掉重复结果
     * @param bool $distinct TRUE，去掉；FALSE，不去
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function distinct($distinct=TRUE){
        $this->_options['distinct']=(bool)$distinct;
        return $this;
    }

    /**
     * 添加Select语段，不使用此方法相当于选择主表中的所有字段
     *
     * @param string|array $fields 如'field1,field2,field3,...' 或 array('f1','f2',$alias=>$f3,...)
     * 字段名:
     * 每个字段名前可以单独加表名或表别名（也可不加），如'{:表名:}.field' 或 '表别名.field' 或 'field'
     * 如字段名可能与关键字冲突，请加上[:冲突:]，如'[:可能冲突的字段名:]' 或 '{:表名:}.[:可能冲突的字段名:]' 或：'表别名.[:可能冲突的字段名:]'
     * 字段名还可以是一个表达式，如 'count( * ) as total'
     * 字段名也可以设置别名，如 'count( * ) as total' 或 array('uid', 'count( uid ) as uid_num')
     * 或用数组的键名代表别名，如 array('u.uid', 'uid_num'=>'count( u.uid )')
     *
     * @param string $table 表名或表别名，例如“{:表名:}”和“表别名”
     * 表名:
     * * 第二个参数是为了统一加上表名（或表别名），从而简化书写形式，但有很多限制：
     * 1、使用此参数时，第一个参数中就不能单独加表名或表别名，否则造成加重复了。
     * 2、第一个中使用了sql内部函数（如count等）也不能统一加表名（或表别名）。
     * 3、当最终的sql语句只操作一张表时，可以省略表名。
     *
     * @return \Monkey\Database\SqlHelper\Select
     *
     * 示例：
     * $select->field('f1,f2,f3');
     * $select->field( array('f1','f2','fAlias'=>'f3') );
     * $select->field('{:user:}.f1,uAlias.f2,{:login:}.f3');
     * $select->field('u.[:name:],u.id,{:login:}.f3');
     * $select->field('count( * ) as total');
     * $select->field(array('u.id','numID'=>'count( u.id )'));
     * $select->field('u.[:name:],u.id,u.f3');
     * $select->field('[:name:],id,f3','u');
     * $select->field('[:name:],id,f3','{:user:}');
     * 还有一些不常用的方式，请参考源码
     */
    public function field($fields,$table=''){
        !empty($table) and $table.='.';
        if(is_array($fields)){
            foreach ($fields as $alias => $field) {
                is_string($alias) and $fields[$alias]=$field.' AS '.$alias;
            }
            $fields=$table.implode(', '.$table, $fields);
        }elseif(!empty($table)){
            $fields=preg_replace('/(,\s*)/','$1'.$table,$fields);
        }
        $this->_options['fields'][]=$table.$fields;
        return $this;
    }
    /**
     * 设置要选择的表名
     * @param string $table 表名(不用带前缀，程序自动加入前缀）
     * @param string $alias 别名
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function table($table,$alias=''){
        $table='[:{:'.$table.':}:]';
        $this->_options['table'][]=empty($alias)? $table : $table.' AS '.$alias;
        return $this;
    }
    /**
     * 添加join语段
     * @param array $table1 连接表名，array('table'=>name,'alias'=>aliasName) 可以只有别名或只有表名
     * @param string $type 连接类型 INNER|LEFT|RIGHT|FULL
     * @param array $table2 连接表名，array('table'=>name,'alias'=>alias) 可以只有别名或只有表名
     * @param string $onField 连接查询条件
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function join(array $table1,$type,array $table2,$onField){
        if(!is_string($type) or !is_string($onField)) return $this;
        $table1=$this->_join_alias($table1);
        $table2=$this->_join_alias($table2);
        $this->_options['join'][]=$table1.' '.$type.' JOIN '.$table2.' ON '.$onField;
        return $this;
    }
    private function _join_alias($table){
        if(!isset($table['table'])) return $table['alias'];
        $alias=isset($table['alias'])?' AS '.$table['alias']:'';
        return '[:{:'.$table['table'].':}:]'.$alias;
    }
    /**
     * 添加where语段
     * @param string $conditions 查询条件。当条件中含有可能注入的内容，将这个内容复制到第二个数组参数中，并用“<对应的键名>”形式，替换原条件中的内容
     * @param null|string|array $params 查询条件中可能注入的内容的替换列表，为字符串时表示所有内容都替换成这个。替换前，程序会自动过滤危险字符。
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function where($conditions,$params=null){
        if(!is_string($conditions))return $this;
        if(!is_null($params))$conditions=$this->parseConditions($conditions,$params);
        $this->_options['where'][]=$conditions;
        return $this;
    }
    /**
     * 添加 group by 语段
     * @param string $field 字段名
     * @param bool $isASC 是否为升序
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function groupby($field, $isASC=TRUE){
        $isASC=$isASC?'ASC':'DESC';
        $this->_options['groupby'][$isASC][]='[:'.$field.':]';
        return $this;
    }
    /**
     * 添加 having 语段
     * @param string $having having条件
     * @param null|string|array $params having条件中可能注入的内容的替换列表，为字符串时表示所有内容都替换成这个。替换前，程序会自动过滤危险字符。
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function having($having,$params=null){
        if(!is_null($params))$having=$this->parseConditions($having,$params);
        $this->_options['having'][]=$having;
        return $this;
    }
    /**
     * 添加 orderby 语段
     * @param string $field 字段名
     * @param bool $isASC 是否为升序
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function orderby($field, $isASC=TRUE){
        $isASC=$isASC?'ASC':'DESC';
        $this->_options['orderby'][$isASC][]='[:'.$field.':]';
        return $this;
    }
    /**
     * 设置 Limit 语段
     * @param integer $length 选择记录条数
     * @param integer $start 起始位置，默认为0（从第一条数据开始选择）
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function limit($length,$start=null){
        $this->_options['limit']=array('start'=>intval($start),'length'=>intval($length));
        return $this;
    }
    /**
     * 设置 top 语段
     * Limit 0 的快捷方式
     * @param integer $top 选择记录条数
     * @return \Monkey\Database\SqlHelper\Select
     */
    public function top($top){
        return $this->limit($top, 0);
    }
    /**
     * 生成sql语句
     * @return string
     */
    public function getSql(){
        return $this->_queryHelper->buildSelect($this->_options);
    }
    /**
     * 执行生成的sql语句
     * @return \Monkey\Database\Driver\_Interface
     */
    public function exec(){
        $options=$this->_options;
        $this->_options=array();
        return $this->_queryHelper->select($options);
    }
    /**
     * 通过缓存执行生成的sql语句
     * @param int $expire 缓存时间，意义见缓存类说明
     * @param null|string|\Monkey\Cache\_Interface $cache 缓存类型或缓存对象，默认为生成默认的缓存对象
     * @return array 二维数据，对应数据库中的行和列。
     */
    public function cache($expire=-1,$cache=''){//加入连接识别参数
        if(empty($cache) || is_string($cache)){
            $cache=CacheManager::getCacheByType($cache);
        }elseif(!($cache instanceof Cache\_Interface)){
            $cache=CacheManager::getCacheByType();
        }
        $sql=$this->_queryHelper->buildSelect($this->_options);
        $this->_options=array();
        if(!$cache->fetch($this->_linkKey.$sql,$data)){
            $data=$this->_queryHelper->query($sql)->resultAll();
            $cache->store($this->_linkKey.$sql,$data,$expire);
        }
        return $data;
    }
    /**
     * 生成统计用的sql语句
     * @param string $columnName 存放统计结果的字段名，默认为'_nums_'。
     * @return string
     */
    public function getSqlOfCount($columnName='_nums_'){
        $options=$this->_options;
        $options['fields']=array('count(*) AS '.$columnName);
        $options['into']=null;
        if(isset($options['limit'])) unset($options['limit']);
        $options['orderby']=null;
        return $this->_queryHelper->buildSelect($options);
    }
    /**
     * 执行生成的sql统计语句。
     * @return array|int
     */
    public function execOfCount(){
        $sql=$this->getSqlOfCount();
        $driver=$this->_queryHelper->query($sql);
        if($driver->isSuccess()){
            $result=$driver->result();
            return $result?$result['_nums_']:0;
        }
        return notice(false,array('msg'=>$driver->errorMessage(),'code'=>$driver->errorCode()));
    }
    /**
     * 解析条件中的参数
     * @param string $conditions 条件
     * @param null|string|array $params 要植入到条件中的参数
     * @return string
     */
    private function parseConditions($conditions,$params=null){
        if(is_null($params))return $conditions;
        $params=$this->_queryHelper->escape($params);
        if(is_array($params)){
            $_params_key=array();
            foreach($params as $key=>$val){
                $_params_key[$key]='<'.$key.'>';
            }
            return str_replace($_params_key, $params, $conditions);
        }else{
            return preg_replace('/<(\S+)>/i', $params, $conditions);
        }
    }
}